package p2p;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import server.Message;

public class Kwalker{
	private FileList fileList;
	private NeighborList neighborList;
	private Peer thisPeer;

	public Kwalker(Peer thisPeer, FileList fileList, NeighborList neighborList){
		this.thisPeer = thisPeer;
		this.fileList = fileList;
		this.neighborList = neighborList;
	}

	public void doKwalker(String fileName, int TTL, int K, Peer fromPeer, List<Peer> knownPeers, String trace){
		System.out.println("Searching:"+fileName+" TTL:"+TTL+" K:"+K+" Peer:"+((fromPeer == null) ? thisPeer.getName() :fromPeer.getName()));
		Registry registry;
		Message rmiServer;

		//Search for the file in our Neighborlist
		List<Peer> listLocalPeer = fileList.findLocalFile(fileName);
		trace = trace+"->"+thisPeer.getName();

		if(listLocalPeer.isEmpty() && TTL > 0){
			System.out.println("Calling nighborlist");
			List<Peer> listNeighborPeers = addNeighborList(neighborList.getNlist().toArray()); 
			if(listNeighborPeers.isEmpty()){
				System.out.println("You don't have any neighbor, Mate!!");
				return;
			}

			//List containing Peers we haven't locked at			
			listNeighborPeers = removeKnownPeers(listNeighborPeers, knownPeers);

			if(listNeighborPeers.isEmpty()){
				try{
					String result = "From "+thisPeer.getName()+"["+thisPeer.getIp()+":"+thisPeer.getPort()+"]: ";
					registry = LocateRegistry.getRegistry(fromPeer.getIp(), fromPeer.getPort()); //get the “registry” 
					rmiServer = (Message)(registry.lookup(Config.ServerName)); //look up remote object
					result = result + "TimeToLive Exceeded, file not found "+fileName+"\n\tTrace: "+trace;
					rmiServer.writeToTerminal(result);
				} catch (NotBoundException e) {
					e.printStackTrace();
				} catch (RemoteException e) {
					e.printStackTrace();
				}
				return;
			}
			int tempKwalk = K;			
			while(tempKwalk > 0 && listNeighborPeers.size() > 0){
				int random = new Random().nextInt(listNeighborPeers.size());
				Peer p = listNeighborPeers.get(random);
				listNeighborPeers.remove(random);

				try {
					registry = LocateRegistry.getRegistry(p.getIp(), p.getPort()); //get the “registry” 
					rmiServer = (Message)(registry.lookup(Config.ServerName)); //look up remote object
					List<Peer> sendListOfPeers = addNeighborList(neighborList.getNlist().toArray());
					if(fromPeer == null && knownPeers == null){ // We received a call from terminal thread too doKwalker 
						sendListOfPeers.add(this.thisPeer);
						rmiServer.search(fileName, TTL-1, K, thisPeer, sendListOfPeers, thisPeer.getName());
					}else{ // We received RMI call too doKwalker
						sendListOfPeers.addAll(knownPeers);
						sendListOfPeers.add(thisPeer);
						HashSet<Peer> hashSet = new HashSet<Peer>(sendListOfPeers);
						List<Peer> removeDup = new ArrayList<Peer>(hashSet);

						rmiServer.search(fileName, TTL-1, K, fromPeer, removeDup, trace);
					}
				} catch (NotBoundException e) {
					e.printStackTrace();
				} catch (RemoteException e) {
					e.printStackTrace();
				}
				tempKwalk--;
			}
		}else{
			if(fromPeer == null){
				String result = "From NeighborList: ";
				for(Peer peer : listLocalPeer){
					result = result + peer.getName()+"["+peer.getIp()+":"+peer.getPort()+"] ";
				}
				result = result + "have the file "+fileName;
				writeToTerminal(result);
			}else{
				try{
					String result = "From "+thisPeer.getName()+"["+thisPeer.getIp()+":"+thisPeer.getPort()+"]: ";
					registry = LocateRegistry.getRegistry(fromPeer.getIp(), fromPeer.getPort()); //get the “registry” 
					rmiServer = (Message)(registry.lookup(Config.ServerName)); //look up remote object
					if(listLocalPeer.isEmpty()){
						result = result + "TimeToLive Exceeded, file not found "+fileName+"\n\tTrace: "+trace;

					}else{
						for(Peer peer : listLocalPeer){
							System.out.println(peer.getName()+"["+peer.getIp()+":"+peer.getPort()+"]");
							result = result + peer.getName()+"["+peer.getIp()+":"+peer.getPort()+"] ";
						}
						result = result + "have the file "+fileName+"\n\tTrace: "+trace;
					}
					rmiServer.writeToTerminal(result);
				} catch (NotBoundException e) {
					e.printStackTrace();
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private List<Peer> addNeighborList(Object[] objects){
		List<Peer> listNeighborPeer = new ArrayList<Peer>(); 
		for(int i = 0; i < objects.length; i++){
			listNeighborPeer.add((Peer) objects[i]);
		}
		return listNeighborPeer;
	}

	private List<Peer> removeKnownPeers(List<Peer> listLocalPeer, List<Peer> knownPeer){
		if(knownPeer == null){
			return listLocalPeer;
		}
		for(Iterator<Peer> itr = listLocalPeer.iterator(); itr.hasNext(); ){
			Peer pLocal = itr.next();
			for(Peer pKnown : knownPeer){
				if(pLocal.getName().equals(pKnown.getName())){
					itr.remove();
				}
			}
		}
		return listLocalPeer;
	}

	public byte[] fileUpload(String fileName){
		fileName = fileList.getFilePath(fileName);
		if(fileName == null){
			return null;
		}
		FileInputStream fin;
		FileChannel fchan;
		ByteBuffer buff;
		long fsize;
		byte bufferFile[] = null;
		try {
			fin = new FileInputStream(fileName);
			fchan = fin.getChannel();
			fsize = fchan.size();
			buff = ByteBuffer.allocate((int)fsize);
			fchan.read(buff);
			buff.rewind();
			bufferFile = new byte[(int)fsize];
			for(int i=0; i<(int)fsize; i++){
				bufferFile[i] = buff.get();
			}
		} catch (Exception e){
			System.out.println("Error: " + e);
		}
		return bufferFile;
	}

	public void fileDownload(byte[] receivedByte, String fileName){
		if(receivedByte == null){
			System.out.println("File doesn't exists at the peer");
			return;
		}

		ByteBuffer buff;
		FileOutputStream fout;
		FileChannel fchan;
		try {
			fout = new FileOutputStream(fileList.getDirectory()+"/"+fileName);
			fchan = fout.getChannel();
			buff = ByteBuffer.allocateDirect((int)(receivedByte.length));
			for(int i=0; i<receivedByte.length; i++) buff.put(receivedByte[i]);
			buff.rewind();
			fchan.write(buff);
			fchan.close();
			fout.close();
			System.out.println("File has been downloaded");
		} catch(Exception e) {   
			e.printStackTrace();
			System.out.println("Error: " + e);
		}
	}

	public void writeToTerminal(String message){
		System.out.println(message);
	}
}
